﻿using System;
using System.Reflection;
using System.Collections.Generic;

namespace BizElements.Core
{
    /// <summary>Creates <see cref="IDbTable"/> objects.</summary>
    /// <remarks><b>DbTableFactory</b> is commonly used when an object that contains a reference to a table object (eg. entity classes)
    /// are serialized and deserialized. To reduce serialization footprint you should consider the following pattern:
    /// <list type="bullet">
    /// <item>Mark <see cref="IDbTable"/> member with <see cref="System.NonSerializedAttribute"/> attribute.</item>
    /// <item>Add three members that contain the following table data: .NET type (not required if you always know which class is used), alias and a flag indicating whether columns' aliases are prefixed with a table alias.</item>
    /// <item>Whenever a table is instantiated, change the previously described members.</item>
    /// <item>Neer access tha table member directly. Instead use a getter that checks if table member has been initialized.</item>
    /// </list>
    /// <example>The following example shows how to implement the pattern discussed above:
    /// <code>
    /// [NonSerialized]
    /// private IDbTable table;
    /// private Type dbTableClass;
    /// private string tableAlias;
    /// private bool columnAliasesArePrefixed;
    /// 
    /// public IDbTable Table
    /// {
    ///     get 
    ///     {
    ///         if (this.table == null)
    ///             this.table = DbTableFactory.CreateTable(this.dbTableClass, this.tableAlias, this.columnAliasesArePrefixed);
    ///         return this.table;
    ///     }
    /// }
    /// </code>
    /// </example>
    /// </remarks>
    public static class DbTableFactory
    {
        static readonly object SyncRoot = new object();
        static readonly Dictionary<Type, IDbTable> PrototypeCache = new Dictionary<Type, IDbTable>();

        /// <summary>Create a specified table object.</summary>
        /// <typeparam name="T">A type that implements <see cref="IDbTable"/> interface.</typeparam>
        /// <param name="alias">Optional object alias. If <b>null</b> then it will be equal to table/view name.</param>
        /// <returns><see cref="IDbTable"/> object.</returns>
        public static T CreateTable<T>(string alias)
            where T : IDbTable, new()
        {            
            IDbTable prototype = GetPrototype<T>();
            return (T)prototype.Clone(alias);
        }

        /// <summary>Create a specified table object.</summary>
        /// <typeparam name="T">A type that implements <see cref="IDbTable"/> interface.</typeparam>
        /// <param name="alias">Optional object alias. If <b>null</b> then it will be equal to table/view name.</param>
        /// <param name="columnAliasesArePrefixed">Specifies whether columns' aliases are prefixed with a table alias.</param>
        /// <returns><see cref="IDbTable"/> object.</returns>
        public static T CreateTable<T>(string alias, bool columnAliasesArePrefixed)
            where T : IDbTable, new()
        {
            IDbTable prototype = GetPrototype<T>();
            return (T)prototype.Clone(alias, columnAliasesArePrefixed);
        }

        private static IDbTable GetPrototype<T>()
            where T : IDbTable, new()
        {
            Type dbTableClass = typeof(T);
            IDbTable prototype;
            lock (SyncRoot)
            {
                PrototypeCache.TryGetValue(dbTableClass, out prototype);
                if (prototype == null)
                {
                    prototype = new T();
                    PrototypeCache[dbTableClass] = prototype;
                }
            }

            return prototype;
        }

        /// <summary>Create a specified table object.</summary>
        /// <param name="dbTableClass">A type that implements <see cref="IDbTable"/> interface.</param>
        /// <param name="alias">Optional object alias. If <b>null</b> then it will be equal to table/view name.</param>
        /// <returns><see cref="IDbTable"/> object.</returns>
        public static IDbTable CreateTable(Type dbTableClass, string alias)
        {
            IDbTable prototype = GetPrototype(dbTableClass);
            return prototype.Clone(alias);
        }

        /// <summary>Create a specified table object.</summary>
        /// <param name="dbTableClass">A type that implements <see cref="IDbTable"/> interface.</param>
        /// <param name="alias">Optional object alias. If <b>null</b> then it will be equal to table/view name.</param>
        /// <param name="columnAliasesArePrefixed">Specifies whether columns' aliases are prefixed with a table alias.</param>
        /// <returns><see cref="IDbTable"/> object.</returns>
        public static IDbTable CreateTable(Type dbTableClass, string alias, bool columnAliasesArePrefixed)
        {
            IDbTable prototype = GetPrototype(dbTableClass);
            return prototype.Clone(alias, columnAliasesArePrefixed);
        }

        private static IDbTable GetPrototype(Type dbTableClass)
        {
            IDbTable prototype;
            lock (SyncRoot)
            {
                PrototypeCache.TryGetValue(dbTableClass, out prototype);
                if (prototype == null)
                {
                    prototype = (IDbTable)Activator.CreateInstance(dbTableClass);
                    PrototypeCache[dbTableClass] = prototype;
                }
            }

            return prototype;
        }
    }
}
